package org.quipu.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.resources.client.DataResource;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTMLTable;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_LEFT;
import static com.google.gwt.user.client.ui.HasHorizontalAlignment.ALIGN_RIGHT;
import static com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_MIDDLE;
import static com.google.gwt.user.client.ui.HasVerticalAlignment.ALIGN_TOP;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.quipu.client.events.AppEvent;
import org.quipu.client.events.AppEventBus;
import org.quipu.client.events.AppEventListener;
import org.quipu.client.events.BookSelectionChangedEvent;
import org.quipu.client.events.LoginEvent;
import org.quipu.client.events.TransactionChangedEvent;
import org.quipu.client.events.TransactionSelectionChangedEvent;
import org.quipu.client.events.TransactionsFilterChangedEvent;
import org.quipu.dto.AccountDto;
import org.quipu.dto.CategoryDto;
import org.quipu.dto.CurrencyDto;
import org.quipu.dto.TagDto;
import org.quipu.dto.TransactionDto;
import org.quipu.dto.TransactionsFilterDto;
import org.quipu.resource.Resources;

public class TransactionsPanel extends AppEventWidget implements AppEventListener {

    private final DateTimeFormat dtf;
    private FlexTable transTable;
    private final Button pagePrevBtn;
    private final Button pageNextBtn;
    private final SimplePopupPanel popupPanel;
    private final Map<Integer, TransactionDto> transactionsRowCache;
    private Collection<TransactionDto> transactionsCache;
    private Long selectedId;
    private final HorizontalPanel hPanel;
    private final VerticalPanel leftPanel;
    private final TransactionEditPanel editPanel;
    private final TransactionsFilterPanel filterPanel;
    private final VerticalPanel rightPanel;
    private int page;
    private final int pageSize;

    public TransactionsPanel(AppEventBus eventBus, AppCache cache) {
        super(eventBus, cache);
        dtf = new DateTimeFormat("dd.MM.yyyy");
        page = 0;
        pageSize = 30;
        hPanel = new HorizontalPanel();
        hPanel.addStyleName("gridPanel");
        hPanel.setHorizontalAlignment(ALIGN_LEFT);
        hPanel.setVerticalAlignment(ALIGN_TOP);

        leftPanel = new VerticalPanel();
        leftPanel.addStyleName("gridPanel");
        leftPanel.setHorizontalAlignment(ALIGN_LEFT);
        leftPanel.setVerticalAlignment(ALIGN_TOP);
        leftPanel.setSize("100%", "100%");
        editPanel = new TransactionEditPanel(eventBus, cache);
        leftPanel.add(editPanel.getWidget());
        filterPanel = new TransactionsFilterPanel(eventBus, cache);
        leftPanel.add(filterPanel.getWidget());
        hPanel.add(leftPanel);

        rightPanel = new VerticalPanel();
        rightPanel.addStyleName("gridPanel");
        rightPanel.setHorizontalAlignment(ALIGN_LEFT);
        rightPanel.setVerticalAlignment(ALIGN_TOP);
        rightPanel.setSize("100%", "100%");
        hPanel.add(rightPanel);

        transactionsRowCache = new HashMap<Integer, TransactionDto>();
        transactionsCache = null;
        selectedId = null;
        transTable = new FlexTable();
        transTable.addClickHandler(new ClickHandler() {
            public void onClick(ClickEvent event) {
                HTMLTable.Cell cell = transTable.getCellForEvent(event);
                selectTransaction(cell.getRowIndex());
            }
        });
        transTable.addStyleName("dataTable");
//        transTable.setBorderWidth(1);

        pagePrevBtn = new Button("<");
        pagePrevBtn.setStylePrimaryName("AddRemoveBtn");
        pagePrevBtn.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                if(page > 0) {
                    page--;
                    updateTransactions();
                }
            }
        });
        pageNextBtn = new Button(">");
        pageNextBtn.setStylePrimaryName("AddRemoveBtn");
        pageNextBtn.addClickHandler(new ClickHandler() {

            public void onClick(ClickEvent event) {
                page++;
                updateTransactions();
            }
        });
        popupPanel = new SimplePopupPanel();
        rightPanel.add(transTable);
    }

    @Override
    public void init() {
        editPanel.init();
        filterPanel.init();
        getEventBus().addListener(BookSelectionChangedEvent.class, this);
        getEventBus().addListener(LoginEvent.class, this);
        getEventBus().addListener(TransactionChangedEvent.class, this);
        getEventBus().addListener(TransactionsFilterChangedEvent.class, this);
    }

    private void updateTransactions() {
        if(getCache().getCurrentBook() != null) {
            TransactionsFilterDto filter = filterPanel.getFilter();
            getMainService().getBookTransactions(getCache().getCurrentBook().getId(), filter, page, pageSize, new AsyncCallback<List<TransactionDto>>() {
                public void onSuccess(List<TransactionDto> transactions) {
                    if(transactions.isEmpty() && page > 0) {
                        page--;
                    } else {
                        transactionsCache = transactions;
                        repaintTable();
                    }
                }

                public void onFailure(Throwable caught) {
                    fireError(caught.getMessage());
                }
            });
        }
    }

    @Override
    public void handleEvent(AppEvent event) {
        if(event != null) {
            if(event instanceof BookSelectionChangedEvent ||
                event instanceof TransactionChangedEvent ||
                event instanceof TransactionsFilterChangedEvent) {
                page = 0;
                updateTransactions();
            }
        }
    }

    @Override
    public Widget getWidget() {
        return hPanel;
    }

    private void repaintTable() {
        transTable.removeAllRows();
        if(!transactionsCache.isEmpty()) {
            Map<Long, Double> totalBalances = new HashMap<Long, Double>();
            Set<Long> usedCurrencyIds = new LinkedHashSet<Long>();
            for(TransactionDto transaction: transactionsCache) {
                if(transaction.getCurrencyId() != null) {
                    usedCurrencyIds.add(transaction.getCurrencyId());
                    totalBalances.put(transaction.getCurrencyId(), 0.0);
                    if(transaction.getConvertCurrencyId() != null) {
                        usedCurrencyIds.add(transaction.getConvertCurrencyId());
                        totalBalances.put(transaction.getConvertCurrencyId(), 0.0);
                    }
                }
            }
            final FlexTable.FlexCellFormatter cellFormatter = transTable.getFlexCellFormatter();
            final FlexTable.RowFormatter rowFormatter = transTable.getRowFormatter();
            int row = 0;
            int col = 0;
            AccountDto account;
            transTable.setWidget(row, col, new Label("№"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label(""));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("Дата"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label(""));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("Со счета"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("На счет"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("Сумма"));
            if(usedCurrencyIds.size() > 1) {
                cellFormatter.setColSpan(row, col, usedCurrencyIds.size());
            } else if(usedCurrencyIds.isEmpty()) {
                cellFormatter.setRowSpan(row, col, 2);
            }
            cellFormatter.addStyleName(row, col, "tableCell");
            col++;
    //        cellFormatter.setAlignment(0, 0, ALIGN_CENTER, ALIGN_MIDDLE);
    //        cellFormatter.setAlignment(0, 1, ALIGN_CENTER, ALIGN_MIDDLE);
    //        transTable.setWidget(row, col++, new Label("Валюта"));
            transTable.setWidget(row, col, new Label("Категория"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("Тэги"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("Описание"));
            cellFormatter.setRowSpan(row, col, 2);
            cellFormatter.addStyleName(row++, col++, "tableCell");
            rowFormatter.addStyleName(0, "tableHeader");
            rowFormatter.addStyleName(1, "tableHeader");

            CurrencyDto currency;
            col = 0;
            if(!usedCurrencyIds.isEmpty()) {
                for(Long curId: usedCurrencyIds) {
                    currency = getCache().getCurrency(curId);
                    if(currency != null) {
                        transTable.setWidget(row, col, new Label(currency.getTitle()));
                        cellFormatter.addStyleName(row, col++, "tableCell");
                    }
                }
                row++;
            }

            Label sumLabel;
            Double sum;
            Double signum;
            transactionsRowCache.clear();
            boolean foundSelected = false;
            for(final TransactionDto transaction: transactionsCache) {
                col = 0;
                transactionsRowCache.put(row, transaction);
                transTable.setWidget(row, col, new Label(String.valueOf(row - 1)));
                cellFormatter.addStyleName(row, col++, "tableCell");
                DataResource imageResource;
                CategoryDto.Type type = null;
                if(TransactionDto.Type.INCOME.equals(transaction.getType())) {
                    signum = 1.0;
                    type = CategoryDto.Type.INCOME;
                    imageResource = Resources.instance.income();
                } else if(TransactionDto.Type.OUTCOME.equals(transaction.getType())) {
                    signum = -1.0;
                    type = CategoryDto.Type.OUTCOME;
                    imageResource = Resources.instance.outcome();
                } else {
                    imageResource = Resources.instance.transfer();
                    //todo: check if transfer is inter-own-accounts or not
                    signum = 0.0;
                }
                Image typeImage = new Image(imageResource.getSafeUri());
                typeImage.setPixelSize(16, 16);
                transTable.setWidget(row, col, typeImage);
                cellFormatter.addStyleName(row, col++, "tableCell");
                transTable.setWidget(row, col, new Label(dtf.format(transaction.getDate())));
                cellFormatter.addStyleName(row, col++, "tableCell");
                if(!transaction.isApproved()) {
                    Image plannedImage = new Image(Resources.instance.notApproved().getSafeUri());
                    plannedImage.setPixelSize(16, 16);
                    transTable.setWidget(row, col, plannedImage);
                }
                cellFormatter.addStyleName(row, col++, "tableCell");
                account = getCache().getAccount(transaction.getSrcAccountId());
                if(account != null) {
                    transTable.setWidget(row, col, new Label(account.getTitle()));
                }
                cellFormatter.addStyleName(row, col++, "tableCell");
                account = getCache().getAccount(transaction.getDstAccountId());
                if(account != null) {
                    transTable.setWidget(row, col, new Label(account.getTitle()));
                }
                cellFormatter.addStyleName(row, col++, "tableCell");
                for(Long curId: usedCurrencyIds) {
                    sumLabel = null;
                    String rateTooltip = null;
                    if(curId.equals(transaction.getCurrencyId())) {
                        sum = transaction.getSum();
                        sumLabel = new Label(StringUtils.formatSum(sum, true));
                        if(transaction.getConvertCurrencyId() != null) {
                            sumLabel.addStyleName("paymentSum");
                            rateTooltip = StringUtils.formatRate(1.0 / transaction.getConvertCurrencyRate());
                        } else {
                            totalBalances.put(curId, totalBalances.get(curId) + sum * signum);
                        }
                    } else if(curId.equals(transaction.getConvertCurrencyId())) {
                        rateTooltip = StringUtils.formatRate(transaction.getConvertCurrencyRate());
                        sum = transaction.getSum() * transaction.getConvertCurrencyRate();
                        sumLabel = new Label(StringUtils.formatSum(sum, true));
                        totalBalances.put(curId, totalBalances.get(curId) + sum * signum);
                    }
                    transTable.setWidget(row, col, sumLabel);
                    if(rateTooltip != null) {
                        setTooltip(sumLabel, rateTooltip);
                    }
                    cellFormatter.setAlignment(row, col, ALIGN_RIGHT, ALIGN_MIDDLE);
                    cellFormatter.addStyleName(row, col++, "tableCell");
                }
                CategoryDto category = getCache().getCategory(type, transaction.getCategoryId());
                if(type != null && category != null) {
                    transTable.setWidget(row, col, new Label(getFullCategoryTitle(type, category)));
                }
                cellFormatter.addStyleName(row, col++, "tableCell");
                String tagStr = "";
                for(TagDto tag: transaction.getTags()) {
                    if(!tagStr.isEmpty()) {
                        tagStr += ", ";
                    }
                    tagStr += tag.getTitle();
                }
                transTable.setWidget(row, col, new Label(tagStr));
                cellFormatter.addStyleName(row, col++, "descriptionCell");
                final Label descLabel;
                if(transaction.getDescription() != null && transaction.getDescription().length() > 20) {
                    descLabel = new Label(transaction.getDescription().substring(0, 19) + "...");
                    setTooltip(descLabel, transaction.getDescription());
                } else {
                    descLabel = new Label(transaction.getDescription());
                }
                transTable.setWidget(row, col, descLabel);
                cellFormatter.addStyleName(row, col++, "descriptionCell");
                if(transaction.getId().equals(selectedId)) {
                    rowFormatter.addStyleName(row, "tableSelectedRow");
                    foundSelected = true;
                }
                rowFormatter.addStyleName(row, "tableSelectableRow");
                row++;
            }

            col = 0;
            pagePrevBtn.setEnabled(page > 0);
            pageNextBtn.setEnabled(transactionsCache.size() >= pageSize);
            transTable.setWidget(row, col++, pagePrevBtn);
            transTable.setWidget(row, col++, pageNextBtn);
            transTable.setWidget(row, col, new Label("page: " + (page + 1)));
            cellFormatter.setColSpan(row, col, 2);
            cellFormatter.addStyleName(row, col++, "tableCell");
            transTable.setWidget(row, col, new Label("Баланс:"));
            cellFormatter.setColSpan(row, col++, 2);
            Double totalBalance;
            for(Long curId: usedCurrencyIds) {
                totalBalance = totalBalances.get(curId);
                transTable.setWidget(row, col, new Label(StringUtils.formatSum(totalBalance, true)));
                cellFormatter.setAlignment(row, col, ALIGN_RIGHT, ALIGN_MIDDLE);
                cellFormatter.addStyleName(row, col++, "tableCell");
            }
            transTable.setWidget(row, col, null);
            cellFormatter.setColSpan(row, col, 3);
            rowFormatter.addStyleName(row, "tableTotal");

            if(!foundSelected) {
                selectedId = null;
                getEventBus().fireEvent(new TransactionSelectionChangedEvent(this, null));
            }
        } else {
            transTable.setWidget(0, 0, new Label("Транзакции, удовлетворяющие заданному фильтру, не найдены"));
        }
    }

    private void setTooltip(final Label label, final String text) {
        label.addMouseOverHandler(new MouseOverHandler() {
            @Override
            public void onMouseOver(MouseOverEvent event) {
                popupPanel.setText(text);
                popupPanel.setPopupPosition(label.getAbsoluteLeft() + 10, label.getAbsoluteTop() + 25);
                popupPanel.show();
            }
        });
        label.addMouseOutHandler(new MouseOutHandler() {
            @Override
            public void onMouseOut(MouseOutEvent event) {
                popupPanel.hide();
            }
        });
    }

    private void selectTransaction(int row) {
        TransactionDto transaction = transactionsRowCache.get(row);
        if(transaction != null) {
            if(transaction.getId().equals(selectedId)) {
                selectedId = null;
                getEventBus().fireEvent(new TransactionSelectionChangedEvent(this, null));
            } else {
                selectedId = transaction.getId();
                getEventBus().fireEvent(new TransactionSelectionChangedEvent(this, transaction));
            }
            repaintTable();
        }
    }

    private String getFullCategoryTitle(CategoryDto.Type type, CategoryDto category) {
        if(category == null) {
            return "";
        } else {
            String fullCategory = category.getTitle();
            if(category.getParentId() != null) {
                CategoryDto parent = getCache().getCategory(type, category.getParentId());
                if(parent != null) {
                    fullCategory = getFullCategoryTitle(type, parent) + " > " + fullCategory;
                }
            }
            return fullCategory;
        }
    }
}
